import pytest
import asyncio
import json
import os
import tempfile
from unittest.mock import Mock, patch, AsyncMock
from pathlib import Path
from datetime import datetime

from src.reporter.Reporter import Reporter


class TestReporter:
    """Reporter 类的单元测试"""

    @pytest.fixture
    def sample_test_cases(self):
        """提供示例测试用例数据"""
        return [
            {
                "id": "test_case_1",
                "toolName": "test_tool_1",
                "input": {"param1": "value1"},
                "description": "Test case 1 description",
                "query": "Test query 1",
                "expect": "success",
                "validation_tool": {
                    "rule_results": [
                        {"rule_passed": True, "rule_error": "passed"},
                        {"rule_passed": False, "rule_error": "failed"}
                    ],
                    "output": "Tool output 1"
                },
                "validation_eval": {
                    "passed": True,
                    "message": "Evaluation passed",
                    "output": "Eval output 1"
                }
            },
            {
                "id": "test_case_2", 
                "toolName": "test_tool_2",
                "input": {"param2": "value2"},
                "description": "Test case 2 description", 
                "query": "Test query 2",
                "expect": "error",
                "validation_tool": {
                    "rule_results": [
                        {"rule_passed": False, "rule_error": "failed"}
                    ],
                    "output": "Tool output 2"
                },
                "validation_eval": {
                    "passed": False,
                    "message": "Evaluation failed",
                    "output": "Eval output 2"
                }
            }
        ]

    @pytest.fixture
    def temp_test_file(self, sample_test_cases):
        """创建临时测试文件"""
        with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
            json.dump(sample_test_cases, f)
            temp_path = f.name
        
        yield temp_path
        # 清理
        if os.path.exists(temp_path):
            os.unlink(temp_path)

    @pytest.fixture
    def mock_config(self):
        """模拟配置文件"""
        return {
            "mcpServers": {
                "test_server": {
                    "command": "python",
                    "args": ["-m", "test_server"],
                    "env": {"TEST_VAR": "test_value"}
                }
            }
        }

    @pytest.fixture
    def reporter(self, temp_test_file, mock_config):
        """创建被测试的报告器实例"""
        with patch('src.client.MakeConfig.Configuration') as mock_config_class:
            mock_config_instance = Mock()
            mock_config_instance.load_config.return_value = mock_config
            mock_config_class.return_value = mock_config_instance
            
            with patch('src.llm.LLM.LLMClient') as mock_llm_class:
                mock_llm_instance = Mock()
                mock_llm_class.return_value = mock_llm_instance
                
                reporter = Reporter(
                    testpath=temp_test_file,
                    config_path="test_config.json",
                    detailed=False,
                    api_key="test_api_key"
                )
                
                yield reporter

    @pytest.fixture
    def detailed_reporter(self, temp_test_file, mock_config):
        """创建详细报告器实例"""
        with patch('src.client.MakeConfig.Configuration') as mock_config_class:
            mock_config_instance = Mock()
            mock_config_instance.load_config.return_value = mock_config
            mock_config_class.return_value = mock_config_instance
            
            with patch('src.llm.LLM.LLMClient') as mock_llm_class:
                mock_llm_instance = Mock()
                mock_llm_class.return_value = mock_llm_instance
                
                reporter = Reporter(
                    testpath=temp_test_file,
                    config_path="test_config.json",
                    detailed=True,
                    api_key="test_api_key"
                )
                
                yield reporter

    def test_initialization(self, reporter):
        """测试初始化是否正确"""
        assert reporter.testpath is not None
        assert reporter.detailed is False
        assert reporter.api_key == "test_api_key"

    def test_initialization_detailed(self, detailed_reporter):
        """测试详细报告器的初始化"""
        assert detailed_reporter.detailed is True

    def test_generate_report_basic(self, reporter, sample_test_cases):
        """测试生成基本报告"""
        report = reporter.generate_report(sample_test_cases)
        
        # 验证报告结构
        assert "tools" in report
        assert "summary" in report
        assert "total_cases" in report["summary"]
        assert "success_cases" in report["summary"]
        assert "failure_cases" in report["summary"]
        
        # 验证统计信息
        assert report["summary"]["total_cases"] == 2
        assert report["summary"]["success_cases"] == 1  # 只有第一个用例期望成功
        assert report["summary"]["failure_cases"] == 1

    def test_generate_report_tool_validation_logic(self, reporter, sample_test_cases):
        """测试工具验证逻辑"""
        report = reporter.generate_report(sample_test_cases)
        
        # 验证工具1的验证结果（2个规则，1个通过）
        tool1_cases = report["tools"]["test_tool_1"]["cases"]
        case1 = list(tool1_cases.values())[0]
        
        assert case1["validation_tool"]["total_rules"] == 2
        assert case1["validation_tool"]["passed_rules"] == 1
        assert case1["validation_tool"]["pass_rate"] == 0.5
        assert case1["validation_tool"]["passed"] is True  # 超过一半通过

    def test_generate_report_empty_cases(self, reporter):
        """测试生成报告（空测试用例）"""
        report = reporter.generate_report([])
        
        assert report["summary"]["total_cases"] == 0
        assert report["summary"]["success_cases"] == 0
        assert report["summary"]["failure_cases"] == 0
        assert len(report["tools"]) == 0

    def test_generate_report_tool_summary(self, reporter, sample_test_cases):
        """测试工具汇总统计"""
        report = reporter.generate_report(sample_test_cases)
        
        # 验证工具汇总
        assert "test_tool_1" in report["summary"]["tools_summary"]
        assert "test_tool_2" in report["summary"]["tools_summary"]
        
        tool1_summary = report["summary"]["tools_summary"]["test_tool_1"]
        assert tool1_summary["total_cases"] == 1
        assert tool1_summary["success_rate"] == 100.0  # 期望成功

    def test_save_to_file(self, reporter, sample_test_cases, temp_test_file):
        """测试保存报告到文件"""
        report = reporter.generate_report(sample_test_cases)
        
        result = reporter.save_to_file(report)
        
        # 验证文件是否创建
        report_path = os.path.join(os.path.dirname(temp_test_file), "report.json")
        assert os.path.exists(report_path)
        
        # 验证文件内容
        with open(report_path, 'r', encoding='utf-8') as f:
            saved_report = json.load(f)
            assert saved_report["summary"]["total_cases"] == 2

    def test_save_to_file_io_error(self, reporter, sample_test_cases):
        """测试保存报告到文件（IO错误）"""
        report = reporter.generate_report(sample_test_cases)
        
        # 模拟 IO 错误
        with patch('builtins.open', side_effect=IOError("Disk full")):
            # 这里应该不会抛出异常，但方法会返回 None 或 False
            # 根据您的实现，可能需要调整
            pass

    def test_print_report(self, reporter, sample_test_cases, temp_test_file, capsys):
        """测试打印报告"""
        report = reporter.generate_report(sample_test_cases)
        
        reporter.print_report(report)
        
        # 捕获标准输出
        captured = capsys.readouterr()
        output = captured.out
        
        # 验证输出包含关键信息
        assert "验证结果汇总报告" in output
        assert "总测试用例数: 2" in output
        assert "工具验证总体通过率" in output
        
        # 验证 Markdown 文件是否创建
        log_path = os.path.join(os.path.dirname(temp_test_file), "test_server_report_summary.md")
        assert os.path.exists(log_path)

    @pytest.mark.asyncio
    async def test_run_basic(self, reporter, sample_test_cases):
        """测试运行基本报告"""
        # 模拟文件读取
        with patch('builtins.open', return_value=Mock(
            __enter__=Mock(return_value=Mock(
                read=Mock(return_value=json.dumps(sample_test_cases))
            )),
            __exit__=Mock(return_value=None)
        )):
            await reporter.run()

    @pytest.mark.asyncio
    async def test_run_detailed(self, detailed_reporter, sample_test_cases):
        """测试运行详细报告"""
        # 模拟文件读取
        with patch('builtins.open', return_value=Mock(
            __enter__=Mock(return_value=Mock(
                read=Mock(return_value=json.dumps(sample_test_cases))
            )),
            __exit__=Mock(return_value=None)
        )):
            # 模拟 MCPClient
            with patch('src.client.MCPClient') as mock_mcp_class:
                mock_mcp_instance = AsyncMock()
                mock_mcp_class.return_value = mock_mcp_instance
                
                # 模拟工具列表
                mock_tool = Mock()
                mock_tool.name = "test_tool_1"
                mock_tool.description = "Test tool 1"
                mock_tool.input_schema = {"properties": {"param1": {"type": "string"}}}
                mock_mcp_instance.list_tools.return_value = [mock_tool]
                
                # 模拟 ReadSourceCode
                with patch('src.utils.read_source_code.ReadSourceCode') as mock_readsc:
                    mock_readsc_instance = Mock()
                    mock_readsc.return_value = mock_readsc_instance
                    mock_readsc_instance.get_code.return_value = {
                        "test_tool_1": "def test_tool_1(param1):\n    return {'result': 'success'}"
                    }
                    
                    await detailed_reporter.run()
                    
                    # 验证详细报告文件是否创建
                    summary_path = os.path.join(
                        os.path.dirname(detailed_reporter.testpath), 
                        "test_server_all_tools_analysis.md"
                    )
                    # 由于我们模拟了文件操作，实际文件可能不会创建，但可以验证方法调用

    @pytest.mark.asyncio
    async def test_generate_report_detail_no_config(self, detailed_reporter, sample_test_cases):
        """测试生成详细报告（无配置的情况）"""
        detailed_reporter.config = {"mcpServers": {}}
        
        report = detailed_reporter.generate_report(sample_test_cases)
        await detailed_reporter.generate_report_detail(sample_test_cases, report)
        
        # 应该记录错误但不会抛出异常

    @pytest.mark.asyncio
    async def test_generate_report_detail_with_tools(self, detailed_reporter, sample_test_cases):
        """测试生成详细报告（有工具的情况）"""
        report = detailed_reporter.generate_report(sample_test_cases)
        
        # 模拟 MCPClient
        with patch('src.client.MCPClient') as mock_mcp_class:
            mock_mcp_instance = AsyncMock()
            mock_mcp_class.return_value = mock_mcp_instance
            
            # 模拟工具列表
            mock_tool = Mock()
            mock_tool.name = "test_tool_1"
            mock_tool.description = "Test tool 1"
            mock_tool.input_schema = {"properties": {"param1": {"type": "string"}}}
            mock_mcp_instance.list_tools.return_value = [mock_tool]
            
            # 模拟 ReadSourceCode
            with patch('src.utils.read_source_code.ReadSourceCode') as mock_readsc:
                mock_readsc_instance = Mock()
                mock_readsc.return_value = mock_readsc_instance
                mock_readsc_instance.get_code.return_value = {
                    "test_tool_1": "def test_tool_1(param1):\n    return {'result': 'success'}"
                }
                
                await detailed_reporter.generate_report_detail(sample_test_cases, report)

    def test_query_id_from_vallist_found(self, reporter, sample_test_cases):
        """测试根据ID查询测试用例（找到的情况）"""
        result = reporter.query_id_from_vallist("test_case_1", sample_test_cases)
        
        assert result is not None
        assert result["id"] == "test_case_1"
        assert result["toolName"] == "test_tool_1"

    def test_query_id_from_vallist_not_found(self, reporter, sample_test_cases):
        """测试根据ID查询测试用例（未找到的情况）"""
        result = reporter.query_id_from_vallist("non_existent_id", sample_test_cases)
        
        assert result is None

    def test_get_tool_found(self, reporter):
        """测试获取工具（找到的情况）"""
        mock_tool_1 = Mock()
        mock_tool_1.name = "test_tool_1"
        mock_tool_2 = Mock()
        mock_tool_2.name = "test_tool_2"
        
        tools = [mock_tool_1, mock_tool_2]
        
        result = reporter.get_tool("test_tool_1", tools)
        
        assert result == mock_tool_1

    def test_get_tool_not_found(self, reporter):
        """测试获取工具（未找到的情况）"""
        mock_tool = Mock()
        mock_tool.name = "test_tool_1"
        tools = [mock_tool]
        
        result = reporter.get_tool("non_existent_tool", tools)
        
        assert result is None

    def test_generate_report_edge_cases(self, reporter):
        """测试生成报告的边界情况"""
        # 测试用例没有 validation_tool
        edge_case = [
            {
                "id": "edge_case_1",
                "toolName": "edge_tool",
                "input": {},
                "description": "Edge case",
                "query": "Edge query",
                "expect": "success",
                # 缺少 validation_tool
                "validation_eval": {"passed": True, "message": "OK"}
            }
        ]
        
        report = reporter.generate_report(edge_case)
        
        # 应该能够处理而不抛出异常
        assert report["summary"]["total_cases"] == 1

    def test_generate_report_invalid_rule_results(self, reporter):
        """测试生成报告（无效的规则结果）"""
        invalid_case = [
            {
                "id": "invalid_case",
                "toolName": "test_tool",
                "input": {},
                "description": "Invalid case",
                "query": "Query",
                "expect": "success",
                "validation_tool": {
                    "rule_results": "not_a_list"  # 无效的规则结果格式
                },
                "validation_eval": {"passed": True, "message": "OK"}
            }
        ]
        
        report = reporter.generate_report(invalid_case)
        
        # 应该能够处理而不抛出异常
        assert report["summary"]["total_cases"] == 1

    @pytest.mark.asyncio
    async def test_run_file_not_found(self, reporter):
        """测试运行报告（文件不存在的情况）"""
        reporter.testpath = "/non/existent/file.json"
        
        # 应该抛出文件不存在的异常
        with pytest.raises(FileNotFoundError):
            await reporter.run()

    def test_server_name_extraction(self, reporter, temp_test_file):
        """测试服务器名称提取"""
        # 模拟不同的文件路径格式
        test_paths = [
            "/path/to/server_20250101/testcases.json",
            "/path/to/server_2025/testcases.json", 
            "/path/to/server_20251231_1230/testcases.json"
        ]
        
        for path in test_paths:
            reporter.testpath = path
            # 这里可以添加对 server_name 提取逻辑的验证

    def test_report_structure_validation(self, reporter, sample_test_cases):
        """测试报告结构验证"""
        report = reporter.generate_report(sample_test_cases)
        
        # 验证每个工具都有正确的结构
        for tool_name, tool_data in report["tools"].items():
            assert "cases" in tool_data
            assert "summary" in tool_data
            assert "total_cases" in tool_data["summary"]
            assert "tool_validation_pass" in tool_data["summary"]
            assert "eval_validation_pass" in tool_data["summary"]
            
            # 验证每个用例都有正确的结构
            for case_id, case_data in tool_data["cases"].items():
                assert "validation_tool" in case_data
                assert "validation_eval" in case_data
                assert "overall_pass" in case_data


if __name__ == "__main__":
    # 运行测试
    pytest.main([__file__, "-v"])